using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Configuration;
using System.Threading;
using System.IO;

namespace PrGrSerwerRozproszony
{
    public class MainLoop
    {
        private int serverPortForServers;
        private int serverPortForUsers;
        private Dictionary<int, IConnection> servers;
        private Dictionary<int, IConnection> users;

        private List<string> serversConnectionString;

        public static char DELIMETER = '#';

        private int serverId = 0;

        public MainLoop()
        {
            servers = new Dictionary<int, IConnection>();
            serversConnectionString = new List<string>();
            users = new Dictionary<int, IConnection>();

            serverPortForServers = int.Parse(ConfigurationManager.AppSettings["ServerPortForServers"]);
            serverPortForUsers = int.Parse(ConfigurationManager.AppSettings["ServerPortForUsers"]);
            
        }

        public void Start()
        {
            lock (servers)
            {
                foreach (string serverAddress in
                    ConfigurationManager.AppSettings["ServerList"].Split(';'))
                {
                    if (serverAddress == String.Empty)
                        continue;

                    IConnection connection = null;
                    try
                    {
                        serversConnectionString.Add(serverAddress);

                        connection = new ServerToServerConnection(serverId,
                            new RemoveServerFromListDelegate(this.RemoveServerFromList),
                            true);

                        connection.TcpClient = CreateConnectedTcpClientFromString(serverAddress);

                        connection.Start();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                    finally
                    {
                        servers.Add(serverId, connection);
                        serverId++;
                    }
                }
            }

            //Create new thread for server listener
            TcpListener tcpServerListener = new TcpListener(this.serverPortForServers);
            new Thread(new ParameterizedThreadStart(StartTcpServerListener)).Start(tcpServerListener);


            TcpListener tcpClientListener = new TcpListener(this.serverPortForUsers);
            tcpClientListener.Start();

            Console.WriteLine("SERVER STARTED");
            while (true)
            {
                try
                {
                    Console.WriteLine("WAITING FOR CONNECTION !");
                    TcpClient client = tcpClientListener.AcceptTcpClient();
                    Console.WriteLine("CONNECTION !");

                    //TODO: add authentication check

                    new Thread(new ParameterizedThreadStart(this.TryToConnectUser)).Start(client);

                    //IConnection connection = new UserToServerConnection();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }

        public void TryToConnectUser(object client)
        {
            try
            {
                TcpClient tcpClient = (TcpClient)client;
                StreamReader reader = new StreamReader(tcpClient.GetStream());
                string[] parsedLine = reader.ReadLine().Split(DELIMETER);

                int userId = int.Parse(parsedLine[0]);
                string pass = parsedLine[1];

                IConnection connection = new UserToServerConnection(userId, new RemoveUserFromListDelegate(this.RemoveUserFromList),
                    new SendMessageToUserDelegate(this.SendMessageToUser));

                connection.TcpClient = tcpClient;
                connection.Start();

                lock (users)
                {
                    Console.WriteLine("USER CONNECTED: "  + userId);
                    users.Add(userId, connection);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void StartTcpServerListener(object listener)
        {
            TcpListener tcpServerListener = (TcpListener)listener;
            tcpServerListener.Start();

            while (true)
            {
                try
                {
                    TcpClient client = tcpServerListener.AcceptTcpClient();
                    IConnection connection = new ServerToServerConnection(serverId, new RemoveServerFromListDelegate(this.RemoveServerFromList),
                        false);

                    connection.TcpClient = client;
                    connection.Start();

                    lock (servers)
                    {
                        servers.Add(serverId++, connection);
                    }

                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }

        private TcpClient CreateConnectedTcpClientFromString(String str)
        {
            string[] parsedServerAddress = str.Split(':');

            TcpClient client = new TcpClient();
            client.Connect(parsedServerAddress[0],
                int.Parse(parsedServerAddress[1]));

            return client;
        }

        public delegate void RemoveUserFromListDelegate(int userId);
        public delegate void RemoveServerFromListDelegate(int serverId, bool reconnect);
        public delegate void SendMessageToUserDelegate(int userId, string message, bool resendToServer);

        public void RemoveUserFromList(int userId)
        {
            lock (users)
            {
                if (users.ContainsKey(userId) == false)
                    users.Remove(userId);
            }
        }

        public void RemoveServerFromList(int serverId_, bool reconnect)
        {
            lock (servers)
            {
                if (servers.ContainsKey(serverId_) == false)
                    servers.Remove(serverId_);

                if (reconnect)
                {
                    IConnection connection = null;
                    try
                    {

                        connection = new ServerToServerConnection(serverId_,
                            new RemoveServerFromListDelegate(this.RemoveServerFromList),
                            true);

                        connection.TcpClient = CreateConnectedTcpClientFromString(serversConnectionString[serverId_]);

                        connection.Start();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                    finally
                    {
                        servers.Add(serverId_, connection);
                    }
                }
            }
        }

        public void SendMessageToUser(int userId, string message, bool resendToServer)
        {
            lock (users)
            {
                IConnection conn = users[userId];
                if(conn != null)
                    conn.SendMessage(message);
                else if (resendToServer)
                {
                    Dictionary<int, IConnection>.ValueCollection collection = null;
                    lock (servers)
                    {
                        collection = servers.Values;
                    }

                    foreach (IConnection connn in collection)
                    {
                        connn.SendMessage(message);
                    }
                }

            }
        }
    }
}
